在 C 語言程式設計中,與使用者或檔案進行互動的核心是輸入 (Input) 與輸出 (Output),統稱為 I/O。C 語言透過其標準函式庫 (Standard Library) 提供了一系列豐富的 I/O 函式。這些函式大多被宣告在 <stdio.h>
這個標頭檔 (Header File) 中,因此在使用它們之前,我們幾乎總會在程式的開頭寫上 #include <stdio.h>
。
本章將探討幾組常用的 I/O 函式,從格式化 I/O、字串 I/O 到字元 I/O,並分析它們的原理、用法與差異。
scanf
與 printf
(格式化 I/O)這是 C 語言中最常用的一對 I/O 函式,它們能夠根據指定的格式來讀取和寫入多種資料型態。
printf(格式字串, ...)
: 將資料(變數、常數等)依照指定的格式轉換成文字,並輸出到標準輸出(通常是螢幕)。scanf(格式字串, ...)
: 從標準輸入(通常是鍵盤)讀取文字,並依照指定的格式將其解析、轉換後存入指定的變數位址中。#include <stdio.h>
int main() {
// === 範例一:讀取與輸出多個整數 ===
printf("請輸入三個整數 (以空格分隔): ");
int input1, input2, input3;
scanf("%d %d %d", &input1, &input2, &input3); // 注意變數名前的 '&'
printf("您輸入的數字是: %d %d %d\n", input1, input2, input3);
// === 範例二:讀取字串 ===
printf("\n請輸入一個單字 (不含空格): ");
char str1[20];
scanf("%s", str1); // 讀取字串時,陣列名稱本身就是位址,不需加 '&'
printf("您輸入的單字是: %s\n", str1);
return 0;
}
scanf
原理與常見行為scanf
會從標準輸入(鍵盤)讀取資料,依照格式控制符(如 %d
, %s
)將輸入的資料存入對應變數。scanf
在讀取時,預設會以空白字元 (Whitespace),包含空格、Tab、換行符等,作為資料的分隔符。當使用 %s
讀取字串時,它會在遇到第一個空白字元時停止讀取。scanf
會讀取並跳過輸入緩衝區中的換行符。&
運算子:scanf 的目的是要「改變」變數的值,所以必須傳遞變數的記憶體位址給它,因此在變數名前要加上取址運算子 &
。唯一的例外是字串陣列,因為陣列名稱本身就代表了其起始位址。printf
原理與常見行為printf
負責格式化輸出資料到螢幕。\n
才會換行。sscanf
與 sprintf
(字串格式化 I/O)這對函式的功能與 scanf/printf 非常相似,但它們的操作對象不是標準輸入/輸出流,而是記憶體中的字串。
sprintf
(目標字串緩衝區, 格式字串, ...): 將格式化的資料「印」到一個字串中,而不是螢幕。sscanf
(來源字串, 格式字串, ...): 從一個字串中「讀取」格式化的資料。#include <stdio.h>
int main() {
char str[10];
int dec;
float pi;
// 使用 sscanf 從字串中解析資料
char input_source[] = "abc 50 3.145";
sscanf(input_source, "%s %d %f", str, &dec, &pi);
printf("從字串 \"%s\" 解析出的資料:\n", input_source);
printf("字串: %s, 整數: %d, 浮點數: %.3f\n\n", str, dec, pi);
// 使用 sprintf 將資料格式化並存入字串
char output_buffer[80];
sprintf(output_buffer, "圓周率的值是: %.3f", pi);
printf("將資料格式化後存入字串,結果為:\n");
printf("%s\n", output_buffer);
return 0;
}
sscanf
原理與常見行為sscanf
是「string scan formatted」,和 scanf
類似,但來源不是鍵盤輸入,而是「一段字串」。scanf
完全一樣。會依空白字元(空格、tab、換行)分隔不同資料欄位。sscanf(str, "%d %f", &a, &b);
可從 str
字串中解析兩個欄位。\n
或結尾。sprintf
原理與常見行為sprintf
是「string print formatted」,會將格式化後的內容寫入指定的字串陣列,而不是螢幕。printf
幾乎一樣,差別在於「輸出目標」是陣列不是螢幕。sprintf
時要特別小心,必須確保目標字串緩衝區 (output_buffer) 足夠大,能夠容納所有格式化後的內容,否則會造成緩衝區溢位 (Buffer Overflow),這是一個嚴重的安全漏洞。更安全的替代方案是 snprintf
,它允許指定最大寫入長度。\n
。gets
與 puts
(行導向 I/O (注意:gets 已被棄用))這組函式以「行」為單位來處理字串。
puts(字串)
: 輸出一行字串到標準輸出,並自動在結尾加上一個換行符 \n。gets(字串緩衝區)
: 從標準輸入讀取一行字串,直到遇到換行符為止。#include <stdio.h>
int main() {
// puts 的使用
char str_s[] = "I love coding.";
puts("--- puts 範例 ---");
puts(str_s); // 會輸出 I love coding. 並換行
// 比較 puts 與 printf
char str_s2[] = "I don't love coding.";
puts("\n--- printf 範例 (無 \\n) ---");
printf("%s", str_s2); // 只輸出 I don't love coding. 不會自動換行
printf(" (printf 輸出的結尾)\n");
/* * gets 的範例在此省略,因為它極度不安全。
* char input_str[10];
* gets(input_str); // 如果使用者輸入超過9個字元,就會發生緩衝區溢位!
*/
return 0;
}
gets
的危險性:gets
函式在讀取輸入時,完全不檢查目標緩衝區的大小。如果使用者輸入的字串長度超過了陣列的容量,多出的字元會覆寫到相鄰的記憶體,引發不可預期的行為或安全漏洞。gets
函式已在 C11 標準中被正式移除。絕對不要在任何新的程式碼中使用 gets
。請永遠使用 fgets
作為替代。puts
的便利性:puts
會自動加上換行符,對於單純輸出整行字串的場景,比 printf
更簡潔。fgets
與 fputs
(安全的檔案/流導向 I/O)這組函式是 gets 和 puts 的安全且更通用的版本,它們可以對任何檔案流 (File Stream) 進行操作,包括標準輸入 (stdin) 和標準輸出 (stdout)。
fgets(緩衝區, 大小, 檔案流)
: 從指定的流讀取一行字串。fputs(字串, 檔案流)
: 將一個字串寫入指定的流。#include <stdio.h>
int main() {
// fgets 從標準輸入讀取一行
char str_fgets[20];
printf("請輸入一行文字 (最多19個字元): ");
fgets(str_fgets, sizeof(str_fgets), stdin); // sizeof(str_fgets) 確保不會溢位
printf("fgets 的讀取結果: %s", str_fgets);
// fputs 寫入到標準輸出
char str_puts[] = "I love you. ";
char str_puts1[] = "And you?";
fputs(str_puts, stdout);
fputs(str_puts1, stdout);
printf("\n");
return 0;
}
fgets
的安全性:fgets
最重要的特性是它的第二個參數(大小)。它告訴函式緩衝區最多能容納多少個字元。fgets 讀取時絕不會超過這個大小減一(保留一個位置給結尾的 \0),從而徹底避免了緩衝區溢位。fgets
會讀取並保留輸入中的換行符 \n
(如果緩衝區空間足夠的話)。這就是為什麼上面範例的 printf
輸出後面通常會多一個換行。fputs
的行為:與 puts
不同,fputs
不會自動在結尾添加換行符。如範例所示,兩次 fputs
的輸出會連在一起。stdin
和 stdout
是在 <stdio.h>
中定義的兩個預設檔案流,分別代表標準輸入和標準輸出。fgets
和 fputs
的設計使其可以輕易地用於檔案讀寫,只需將 stdin/stdout
替換成一個用 fopen()
開啟的檔案指標即可。getchar/putchar
與 getc/putc
(字元導向 I/O)這幾組函式是 C 語言中最基礎的 I/O,它們一次只處理一個字元。getchar()
: 等同於 getc(stdin),從標準輸入讀取一個字元。putchar(字元)
: 等同於 putc(字元, stdout),將一個字元寫入標準輸出。
#include <stdio.h>
int main() {
printf("請輸入一個字元: ");
char c = getc(stdin);
printf("putc 的輸出: ");
putc(c, stdout);
// 清除輸入緩衝區中的換行符
while (getchar() != '\n');
printf("\n\n請再輸入一個字元: ");
char b = getchar();
printf("putchar 的輸出: ");
putchar(b);
printf("\n");
return 0;
}
getchar()
讀取到的是字元 '1'(其 ASCII 碼為 49),而不是整數值 1。getchar/putchar
是 getc/putc
針對標準輸入輸出的特化版本(巨集定義),使用上更簡潔。而 getc/putc
則更通用,可以操作任何檔案流。getchar
從中讀取第一個字元後,其餘的字元(包括 \n
)仍會留在緩衝區中,可能會影響下一次的輸入讀取。範例中 while (getchar() != '\n');
是一種常見的清空緩衝區的方法。指令 | 輸入/輸出 | 自動換行 | 空格行為 | 何時結束 | 備註 |
---|---|---|---|---|---|
puts | 輸出 | ✅ | 照常輸出 | \\0 |
每次自動加換行 |
printf | 輸出 | ❌ | 照常輸出 | 依格式字串 | 需手動加 \\n |
fgets | 輸入 | - | ✅ | 長度或換行或結尾 | 換行符也會存進陣列 |
fputs | 輸出 | ❌ | 照常輸出 | \\0 |
不自動加換行 |
getc | 輸入 | - | ✅ | 每次讀一字元 | getc(stdin) = getchar() |
putc | 輸出 | ❌ | ✅ | 每次寫一字元 | putc(c, stdout) = putchar(c) |
getchar | 輸入 | - | ✅ | 每次讀一字元 | 只從 stdin |
putchar | 輸出 | ❌ | ✅ | 每次寫一字元 | 只到 stdout |